package com.zzb.valid.config;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zzb.valid.dto.ResultEntity;
import com.zzb.valid.util.I18nUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.annotation.Nonnull;
import javax.validation.ConstraintViolation;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;

/**
 * 全局异常控制.
 * {@link org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice} 对返回消息体进行再自定义组装.
 * @author create by zhouzongbo on 2020/10/27.
 */
@Slf4j
@RestControllerAdvice
public class CustomerControllerAdvice implements ResponseBodyAdvice<Object> {

    /**
     * 支持的HttpMessageConvert转换类型集合.
     */
    private static final List<Class<? extends AbstractHttpMessageConverter<?>>> SUPPORT_CONVERT_TYPES
        = Arrays.asList(MappingJackson2HttpMessageConverter.class, StringHttpMessageConverter.class);

    /**
     * Jackson 转换器
     */
    private final ObjectMapper objectMapper;

    public CustomerControllerAdvice(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ResultEntity<?> test(MethodArgumentNotValidException ex) {
        String errorMessage = buildErrorMessage(ex.getBindingResult());
        return ResultEntity.failure(errorMessage);
    }

    /**
     * 组装自定义异常消息, 适配国际化i18n.
     * @param result result
     * @return 异常信息.
     */
    private String buildErrorMessage(BindingResult result) {
        StringBuilder builder = new StringBuilder();
        for (FieldError error : result.getFieldErrors()) {
            final String defaultMessage = error.getDefaultMessage();
            if (hasDefaultMessage(error)) {
                builder.append(defaultMessage).append("\n");
            } else {
                // 从第二个属性起, 是注解属性值
                final Object[] arguments = error.getArguments();
                Object[] args;
                if (arguments == null || 1 == arguments.length) {
                    args = new Object[0];
                } else {
                    args = new Object[arguments.length - 1];
                    System.arraycopy(arguments, 1, args, 0, args.length);
                }
                // 获取错误信息.
                final String code = I18nUtil.getMessage(error.getCode(), args);
                final String field = I18nUtil.getMessage(error.getField(), "", args);
                if (StringUtils.isEmpty(field)) {
                    builder.append("[").append(error.getField()).append("]: ");
                } else {
                    builder.append(field);
                }
                builder.append(StringUtils.isEmpty(code) ? defaultMessage : code).append("\n");
            }
        }
        for (ObjectError globalError : result.getGlobalErrors()) {
            builder.append(globalError.getDefaultMessage()).append("\n");
        }
        return builder.toString();
    }

    /**
     * 是否有默认的消息
     * @param fieldError fieldError
     * @return true:有默认的消息 @NotNull(message = "xxx不能为空.")
     */
    private boolean hasDefaultMessage(FieldError fieldError) {
        Object source;
        try {
            final Field field = ObjectError.class.getDeclaredField("source");
            field.setAccessible(true);
            source = field.get(fieldError);
        } catch (Exception ex) {
            return false;
        }

        return source instanceof ConstraintViolation
                && !((ConstraintViolation<?>)source).getMessageTemplate().contains("constraints");
    }

    @Override
    public boolean supports(@Nonnull MethodParameter returnType,
                            @Nonnull Class<? extends HttpMessageConverter<?>> converterType) {
        // 不是ResultEntity类型才会去包装成ResultEntity类型
        Class<?> cls = ResultEntity.class;
        return SUPPORT_CONVERT_TYPES.contains(converterType) && !cls.isAssignableFrom(returnType.getParameterType());
    }

    @Override
    public Object beforeBodyWrite(Object body, @Nonnull MethodParameter returnType, @Nonnull MediaType selectedContentType,
        @Nonnull Class<? extends HttpMessageConverter<?>> selectedConverterType, @Nonnull ServerHttpRequest request,
        @Nonnull ServerHttpResponse response) {
        if (StringHttpMessageConverter.class.equals(selectedConverterType)) {
            // String类型的转换进行特殊处理.
            request.getHeaders().setContentType(MediaType.APPLICATION_JSON);
            Object boxed;
            try {
                boxed = objectMapper.writeValueAsString(body);
            } catch (JsonProcessingException e) {
                log.error("json格式转换异常 {}", e.getMessage());
                boxed = body;
            }
            return boxed;
        }

        return ResultEntity.success(body);
    }
}
